package com.binance.healthcenter.service;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.*;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import com.binance.healthcenter.model.HealthCheckResult;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import lombok.extern.log4j.Log4j2;

@Log4j2
@Service
public class CAdvisorService {
    @Qualifier("healthCheckRestTemplate")
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private DiscoveryClientService discoveryClientService;

    private static final ExecutorService executorService = Executors.newFixedThreadPool(20);

    public static final String ECS_SUBCONTAINERS_URL = "/api/v1.0/containers/ecs";

    public static final String BASE_ECS_SUBCONTAINERS_URL = "/api/v1.0/containers";

    public static final String BASE_ECS_SUBCONTAINERS_URL_V11 = "/api/v1.1/subcontainers";

    public static final Integer CADVISOR_DEFAULT_PORT = 8080;



    /**
     * 获取这个宿主机上所挂在的容器的路径
     */
    public List<String> getSubContainersUrl(String ipAddr, Integer port, String apiUrl) {
        String requestUrl = "http://" + ipAddr + ":" + String.valueOf(port) + apiUrl;
        log.info("CAdvisorService.getSubContainersUrl :request={}", requestUrl);
        Map<String, Object> requestMap = Maps.newHashMap();
        requestMap.put("num_stats", 1);
        requestMap.put("num_samples", 0);
        HashMap<String, Object> getForObject = restTemplate.postForObject(requestUrl, requestMap, HashMap.class);
        log.info("CAdvisorService.getSubContainersUrl :request={},response={}", requestUrl,getForObject);
        List<HashMap<String, String>> subContainersList =
                (List<HashMap<String, String>>) getForObject.get("subcontainers");
        if (CollectionUtils.isEmpty(subContainersList)) {
            return Lists.newArrayList();
        }
        List<String> subContainerUrlList = Lists.newArrayList();
        for (HashMap<String, String> hashMap : subContainersList) {
            String subContainersUrl = hashMap.get("name");
            if (StringUtils.isBlank(subContainersUrl)) {
                continue;
            }
            subContainerUrlList.add(subContainersUrl);
        }
        log.info("CAdvisorService.getSubContainersUrl :subContainersList={}", subContainerUrlList);
        return subContainerUrlList;
    }

    /**
     * 获取这个宿主机上所挂在的容器的路径
     */
    public HealthCheckResult getContainersInfo(String ipAddr, Integer port, String apiUrl) {
        String requestUrl = "http://" + ipAddr + ":" + String.valueOf(port) + apiUrl;
        log.info("CAdvisorService.getContainersInfo :request={}", requestUrl);
        Map<String, Object> requestMap = Maps.newHashMap();
        requestMap.put("num_stats", 1);
        requestMap.put("num_samples", 0);
        List<HashMap<String, Object>> postForObject = restTemplate.postForObject(requestUrl, requestMap, List.class);
        log.info("CAdvisorService.getContainersInfo :request={},response={}", requestUrl,postForObject);
        HealthCheckResult healthCheckResult = generateHealthCheckResult(postForObject, ipAddr);
        return healthCheckResult;
    }

    public String getServiceName(List<HashMap<String, Object>> hashMaps) {
        for (HashMap<String, Object> hashMap : hashMaps) {
            HashMap<String, String> labelsMap = (HashMap<String, String>) hashMap.get("labels");
            String serviceName = labelsMap.get("com.amazonaws.ecs.container-name");
            return serviceName;
        }
        return null;
    }

    public Pair<Boolean, String> getCpuLoad(List<HashMap<String, Object>> hashMaps) {
        for (HashMap<String, Object> hashMap : hashMaps) {
            List<HashMap<String, Object>> statsMapList = (List<HashMap<String, Object>>) hashMap.get("stats");
            for (HashMap<String, Object> statsMap : statsMapList) {
                HashMap<String, Object> cpuMap = (HashMap<String, Object>) statsMap.get("cpu");
                HashMap<String, Object> usageMap = (HashMap<String, Object>) cpuMap.get("usage");
                Long total = convertObjectToLong(usageMap.get("total"));
                Long user = convertObjectToLong(usageMap.get("user"));
                Long system = convertObjectToLong(usageMap.get("system"));
                MathContext mc = new MathContext(8, RoundingMode.HALF_UP);
                BigDecimal cpuLoadRate = new BigDecimal(user).divide(new BigDecimal(total), mc);
                String currentRateStr = cpuLoadRate.multiply(new BigDecimal(100)).toString();
                if (cpuLoadRate.compareTo(new BigDecimal(2)) >= 0) {
                    String errorMessage = "cpuLoadRate overload currentRate=" + currentRateStr + "%";
                    return Pair.of(false, errorMessage);
                } else {
                    return Pair.of(true, null);
                }
            }
        }
        return null;
    }

    public Pair<Boolean, String> getMemoryLoadInfo(List<HashMap<String, Object>> hashMaps) {
        for (HashMap<String, Object> hashMap : hashMaps) {

            HashMap<String, Object> specMap = (HashMap<String, Object>) hashMap.get("spec");
            Long memoryLimit=null;
            HashMap<String, Object> memoryConfigMap = (HashMap<String, Object>) specMap.get("memory");
            memoryLimit=convertObjectToLong(memoryConfigMap.get("limit"));
            if(null==memoryLimit){
                return Pair.of(true, null);
            }

            List<HashMap<String, Object>> statsMapList = (List<HashMap<String, Object>>) hashMap.get("stats");
            for (HashMap<String, Object> statsMap : statsMapList) {
                HashMap<String, Object> memoryMap = (HashMap<String, Object>) statsMap.get("memory");
                Long usage = convertObjectToLong(memoryMap.get("usage"));
                MathContext mc = new MathContext(8, RoundingMode.HALF_UP);
                BigDecimal memoryLoadRate = new BigDecimal(usage).divide(new BigDecimal(memoryLimit), mc);
                String currentMemoryLoadRateStr = memoryLoadRate.multiply(new BigDecimal(100)).toString();
                if (memoryLoadRate.compareTo(new BigDecimal(1)) >= 0) {
                    String errorMessage = "memory overload currentRate=" + currentMemoryLoadRateStr + "%";
                    return Pair.of(false, errorMessage);
                } else {
                    return Pair.of(true, null);
                }
            }
        }
        return null;
    }

    /**
     * 针对各种类型转换成long型
     */
    private Long convertObjectToLong(Object value) {
        if (value instanceof Double) {
            return ((Double) value).longValue();
        } else if (value instanceof String) {
            return Long.valueOf((String) value);
        } else if (value instanceof Long) {
            return (Long) value;
        } else if (value instanceof Integer) {
            return ((Integer) value).longValue();
        } else if (value instanceof Float) {
            return ((Float) value).longValue();
        } else if (value instanceof Short) {
            return ((Short) value).longValue();
        } else {
            return null;
        }
    }

    /**
     * 产生healthcheckresult的数据
     */
    public HealthCheckResult generateHealthCheckResult(List<HashMap<String, Object>> hashMaps, String host) {
        HealthCheckResult healthCheckResult = new HealthCheckResult();
        String serviceName = getServiceName(hashMaps);
        Pair<Boolean, String> cpuResult = getCpuLoad(hashMaps);
        Pair<Boolean, String> memoryResult = getMemoryLoadInfo(hashMaps);
        healthCheckResult.setServiceName(serviceName);
        healthCheckResult.setHost(host);
        log.info("CAdvisorService.generateHealthCheckResult :serviceName={},host={},cpuResult={},memoryResult={}",
                serviceName,host,cpuResult,memoryResult);
        HashMap<String, Object> exceptionResult = Maps.newHashMap();
        if (null != cpuResult && !cpuResult.getLeft()) {
            healthCheckResult.setStatus("DOWN");
            exceptionResult.put("status", "DOWN");
            exceptionResult.put("result", cpuResult.getRight());
            healthCheckResult.setResultMap(exceptionResult);
        } else if (null != memoryResult && !memoryResult.getLeft()) {
            healthCheckResult.setStatus("DOWN");
            exceptionResult.put("status", "DOWN");
            exceptionResult.put("result", memoryResult.getRight());
            healthCheckResult.setResultMap(exceptionResult);
        } else {
            healthCheckResult.setStatus("UP");
        }
        log.info("CAdvisorService.generateHealthCheckResult :healthCheckResult={}", healthCheckResult);
        if("DOWN".equalsIgnoreCase(healthCheckResult.getStatus())&& null!=healthCheckResult.getResultMap()){
                Object result=healthCheckResult.getResultMap().get("result");
                String resultStr=(String)result;
                if(StringUtils.isBlank(resultStr)){
                    log.info("CAdvisorService.generateHealthCheckResult :filterResultSuccess healthCheckResult={}", healthCheckResult);
                    return null;
                }
        }
        return healthCheckResult;
    }

    public List<HealthCheckResult> requestContainerInfo(String host) {
        List<String> ecsSubContainersUrlList = getSubContainersUrl(host, CADVISOR_DEFAULT_PORT, ECS_SUBCONTAINERS_URL);
        List<String> ecsSubSubContainersUrlList = Lists.newArrayList();
        for (String ecsSubContainersUrl : ecsSubContainersUrlList) {
            String apiUrl = BASE_ECS_SUBCONTAINERS_URL + ecsSubContainersUrl;
            List<String> subContainersList = getSubContainersUrl(host, CADVISOR_DEFAULT_PORT, apiUrl);
            ecsSubSubContainersUrlList.addAll(subContainersList);
        }
        List<HealthCheckResult> healthCheckResultList = Lists.newArrayList();
        for (String subSubcontainersUrl : ecsSubSubContainersUrlList) {
            try {
                HealthCheckResult healthCheckResult = getContainersInfo(host, CADVISOR_DEFAULT_PORT,
                        BASE_ECS_SUBCONTAINERS_URL_V11 + subSubcontainersUrl);
                if(null!=healthCheckResult){
                    healthCheckResultList.add(healthCheckResult);
                }
            } catch (Exception e) {
                log.error("CAdvisorService.requestContainerInfo exception:", e);
            }
        }
        return healthCheckResultList;
    }

    /**
     * 获取所有微服务的healthcheck列表
     */
    public List<HealthCheckResult> getAllHealthInfo() {
        Set<String> serviceHostList = discoveryClientService.getServiceHostSet();
        List<HealthCheckResult> healthCheckResultList = getHealthInfoList(serviceHostList);
        return healthCheckResultList;
    }


    private List<HealthCheckResult> getHealthInfoList(Set<String> serviceHostList) {
        List<HealthCheckResult> healthCheckResultList = Lists.newArrayList();
        CompletionService<List<HealthCheckResult>> completionService =
                new ExecutorCompletionService<List<HealthCheckResult>>(executorService);
        for (String serviceHost : serviceHostList) {
            completionService.submit(new Callable<List<HealthCheckResult>>() {
                @Override
                public List<HealthCheckResult> call() throws Exception {
                    return requestContainerInfo(serviceHost);
                }
            });
        }
        // 获取所有结果
        for (int i = 0; i < serviceHostList.size(); i++) {
            try {
                Future<List<HealthCheckResult>> future = completionService.take();
                List<HealthCheckResult> healthCheckResults = future.get(30, TimeUnit.SECONDS);
                if (org.apache.commons.collections.CollectionUtils.isNotEmpty(healthCheckResults)) {
                    log.info("CAdvisorService.getHealthInfoList:takeResult healthCheckResults={}", healthCheckResults);
                    healthCheckResultList.addAll(healthCheckResults);
                }
            } catch (Exception e) {
                log.error("CAdvisorService.getHealthInfoList completionService.take() timeout", e);
            }
        }
        return healthCheckResultList;
    }



    /**
     * 获取所有不健康的服务列表
     */
    public List<HealthCheckResult> getAllUnHealthInfo() {
        List<HealthCheckResult> healthCheckResultList = getAllHealthInfo();
        List<HealthCheckResult> unHealthCheckResultList = Lists.newArrayList();
        for (HealthCheckResult healthCheckResult : healthCheckResultList) {
            if ("DOWN".equalsIgnoreCase(healthCheckResult.getStatus())) {
                unHealthCheckResultList.add(healthCheckResult);
            }
        }
        return unHealthCheckResultList;
    }
}
